## -*- mode: html; coding: utf-8; -*-
## This file is part of CDS Invenio.
## Copyright (C) 2002, 2003, 2004, 2005, 2006, 2007, 2008 CERN.
##
## CDS Invenio is free software; you can redistribute it and/or
## modify it under the terms of the GNU General Public License as
## published by the Free Software Foundation; either version 2 of the
## License, or (at your option) any later version.
##
## CDS Invenio is distributed in the hope that it will be useful, but
## WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
## General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with CDS Invenio; if not, write to the Free Software Foundation, Inc.,
## 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.

<!-- WebDoc-Page-Title: Error Library -->
<!-- WebDoc-Page-Navtrail: <a class="navtrail" href="<CFG_SITE_URL>/help/hacking">Hacking CDS Invenio</a> &gt; <a class="navtrail" href="miscutil-internals">MiscUtil Internals</a> -->
<!-- WebDoc-Page-Revision: $Id$ -->

<p>
These are the functions and methodologies for error handling in CDS Invenio.
</p>

<h2>Contents</h2>
<ol>
<li><a href="#overview">Overview</a></li>
<li><a href="#creating">Creating errors</a></li>
<li><a href="#using">Using error library</a></li>
<li><a href="#troubleshooting">Troubleshooting</a></li>
</ol>

<h2>1. <a name="overview">Overview</a></h2>

<p>This API handles two concepts: Errors and Warnings.

<p>An error is an unexpected behavior that leads to the stopping of current process.
Discussing of web pages, errors should be displayed by instead of the
requested page. Errors are logged into <code>cds-invenio/var/log/cds-invenio.err</code>.
Errors can be logged with client information and a tracestack.</p>

<p>A warning is an unexpected behavior that can be ignored. Warnings are logged into
<code>cds-invenio/var/log/cds-invenio.log</code> with just the warning message.</p>

<p>Errors and warnings should be internationalized (see <a href="#i18n">below</a>).</p>


<h2>2. <a name="creating">Creating errors</a></h2>
<h3>2.1 Configuration file</h3>
<p>Every module should create a file containing error definitions, warning
definitions, variables avoiding &quot;magic&quot; number or strings, etc.</p>

<p>This file has to be named against a convention:</p>
<pre>
    &lt;module-name&gt;&#95;config.py
</pre>
<p>e.g. <code>webmessage&#95;config.py</code> for the WebMessage module.</p>
<h3>2.2 Dictionaries of errors</h3>
<p>Errors and warnings are eventually stored into dictionaries. The dictionaries
are to be named against the following convention:</p>
<pre>
    CFG&#95;&lt;MODULENAME&gt;&#95;ERROR&#95;MESSAGES and
    CFG&#95;&lt;MODULENAME&gt;&#95;WARNING&#95;MESSAGES
</pre>
<p>These two dictionaries (one can choose to implement only one if he doesn&apos;t
need warnings, for example) contain an error-name -&gt; displayable message
association.</p>

<p>Errors are to be named against the following convention:</p>
<pre>
    ERR&#95;&lt;MODULE-NAME&gt;&#95;ERROR&#95;NAME
</pre>
<p>Please note the use of uppercase.</p>

<p>Warnings can also be named as errors if convenient, and so have to
follow one of these rules:</p>
<pre>
    WRN&#95;&lt;MODULE-NAME&gt;&#95;WARNING&#95;NAME or
    ERR&#95;&lt;MODULE-NAME&gt;&#95;WARNING&#95;NAME
</pre>
<p>The associated message can obviously contain substituted variables like <code>%s</code>, <code>%d</code>...

<h3><a name="i18n">Internationalization</a></h3>
<p>Errors should also be internationalized. As the config file cannot receive
parameters, this is done by the error handling library. The convenient way that has
been chosen is to nest the classical <code>&#95;()</code> function inside the string.</p>
<p>An internationalized error message should look like this:</p>
<pre>
    '&#95;("Internationalized error (%s) message")'
</pre>
<p>A complete example of correct dictionary is given below:</p>
<pre>
    CFG&#95;WEBCOMMENT&#95;ERROR&#95;MESSAGES =
    {   'ERR&#95;WEBCOMMENT&#95;RECID&#95;INVALID'       :  '&#95;("%i is an invalid record ID")',
        'ERR&#95;WEBCOMMENT&#95;RECID&#95;NAN'           :  '&#95;("Record ID %i is not a number")',
        'ERR&#95;WEBCOMMENT&#95;UID&#95;INVALID'         :  '&#95;("%i is an invalid user ID")'
    }
</pre>

<h2>3. <a name="using">Using error library</a></h2>

<h3>3.1 From a web interface</h3>
<p>When displaying a page, the <code>modules/webstyle/lib/webpage.py</code> python module should
be used. This module provides a <code>page()</code> function, convenient for webpage output,
which can handle errors (display and log).<br />
A call to this function should use the following arguments, assuming that language
information is stored in a variable called <code>ln</code>, and request information
are stored in req (will be used for IP logging, for example):</p>
<pre>
    page(...,
         req=req,
         language=ln,
         errors=error&#95;list,
         warnings=warnings&#95;list,
         ...)
</pre>
<p>list of errors and warnings are behaving the same way. They are lists of tuples:</p>
<pre>
    [(error&#95;name, param1, ..., paramN), ...]
</pre>
<p>The params are used to represent substitued values in messages. For example if
you want to throw one of the errors above, error&#95;list should look like this:</p>
<pre>
    error&#95;list = [('ERR&#95;WEBCOMMENT&#95;RECID&#95;INVALID', 123456)]
</pre>
<h4>Example</h4>
<p>Business logic should be separated from web interface. We consider three files in the
following (real) example:
<ol>
<li><code>webmessage_webinterface.py</code>, which is the page as viewed by a browser,</li>
<li><code>webmessage.py</code>, which contains the business logic,</li>
<li><code>webmessage&#95;config</code>, which contains error definitions</li>
</ol>
<p>In this example, a user tries to read a message. We must ensure he doesn't
read another message, and that this message really exist in the system. For
a more convenient reading, some (non error-related) parts of code have been suppressed.</p>
<h5>webmessage&#95;config.py</h5>
<pre>
&#35; error messages. (should not happen, except in case of reload, or url altering)
CFG&#95;WEBMESSAGE&#95;ERROR&#95;MESSAGES = \
{   'ERR&#95;WEBMESSAGE&#95;NOTOWNER':  '&#95;("This message is not in your mailbox")',
    'ERR&#95;WEBMESSAGE&#95;NONICKNAME':'&#95;("No nickname or user for uid #%s")',
    'ERR&#95;WEBMESSAGE&#95;NOMESSAGE': '&#95;("This message doesn\'t exist")'
}
</pre>

<h5>webmessage.py: business logic</h5>
<pre>
from invenio.webmessage&#95;config import CFG&#95;WEBMESSAGE&#95;ERROR&#95;MESSAGES

def perform&#95;request&#95;display&#95;msg(uid, msgid, ln=CFG_SITE_LANG):
    uid   = wash&#95;url&#95;argument(uid, 'int')
    msgid = wash&#95;url&#95;argument(msgid, 'int')
    ln    = wash&#95;language(ln)
    errors = []
    warnings = []
    body = ""
    if (check&#95;user&#95;owns&#95;message(uid, msgid) == 0):
        &#35; The user doesn't own this message
        errors.append(('ERR&#95;WEBMESSAGE&#95;NOTOWNER',))
    else:
        (msg&#95;id, ...) = get&#95;message(uid, msgid)
        if (msg&#95;id == ""):
	    &#35; The message exists in table user&#95;msgMESSAGE
	    &#35; but not in table msgMESSAGE => table inconsistency
            errors.append(('ERR&#95;WEBMESSAGE&#95;NOMESSAGE',))
        else:
            body = webmessage&#95;templates.tmpl&#95;display&#95;msg( ... )
    return (body, errors, warnings)
</pre>

<h5>webmessage_webinterface.py: web interface</h5>
<pre>
from invenio.webpage import page
from invenio.webmessage import perform&#95;request&#95;display&#95;msg

def display&#95;msg(req, msgid=-1, ln=CFG_SITE_LANG):
    &#95; = gettext&#95;set&#95;language(ln)
    # Generate content
    (body, errors, warnings) = perform&#95;request&#95;display&#95;msg(uid, msgid, ln)
    title = &#95;("Read a message")
    return page(title       = title,
                body        = body,
                navtrail    = get&#95;navtrail(ln, title),
                uid         = uid,
                lastupdated = &#95;&#95;lastupdated&#95;&#95;,
                req         = req,
                language    = ln,
                errors      = errors,
                warnings    = warnings)

</pre>

<h3>3.2 From a command line interface</h3>

<p>The following functions can be useful (see source code for other functions):</p>
<pre>
   get&#95;msgs&#95;for&#95;code&#95;list(code&#95;list, stream='error', ln=CFG_SITE_LANG)
        Returns formatted strings for the given errors
        @param code&#95;list: list of tuples  [(err&#95;name, arg1, ..., argN), ...]
        @param stream: 'error' or 'warning'
        @return list of tuples of length 2 [('ERR&#95;...', err&#95;msg), ...]
                if code&#95;list empty, will return None.
                if errors retrieving error messages, will append an error to
                the list

    register&#95;errors(errors&#95;or&#95;warnings&#95;list, stream, req=None)
        log errors to invenio.err and warnings to invenio.log
        errors will be logged with client information (if req is given)
        and a tracestack
        warnings will be logged with just the warning message
        @param errors&#95;or&#95;warnings&#95;list: list of tuples (err&#95;name, err&#95;msg)
        @param stream: 'error' or 'warning'
        @param req = mod&#95;python request
        @return integer 1 if successfully wrote to stream, integer 0 if not
                will append another error to errors&#95;list if unsuccessful

    send&#95;error&#95;report&#95;to&#95;admin(header, url, time, browser, client,
                               error, sys&#95;error, traceback)
        Sends an email to the admin with client info and tracestack
</pre>
<h4>Example</h4>
<p>In the following example, two files are used:</p>
<ol>
<li><code>webmessage&#95;config</code>, containing error messages</li>
<li><code>webmessage&#95;example&#95;bin.py</code>, containing business logic</li>
</ol>
<p>Scenario: a function receives an error and wants to register it only if it is not a
messaging error</p>
<h5>webmessage&#95;config.py</h5>
<pre>
&#35; error messages. (should not happen, except in case of reload, or url altering)
CFG&#95;WEBMESSAGE&#95;ERROR&#95;MESSAGES = \
{   'ERR&#95;WEBMESSAGE&#95;NOTOWNER':  '&#95;("This message is not in your mailbox")',
    'ERR&#95;WEBMESSAGE&#95;NONICKNAME':'&#95;("No nickname or user for uid #%s")',
    'ERR&#95;WEBMESSAGE&#95;NOMESSAGE': '&#95;("This message doesn\'t exist")'
}
</pre>

<h5>webmessage&#95;example&#95;bin.py</h5>
<pre>
from invenio.webmessage&#95;config import CFG&#95;WEBMESSAGE&#95;ERROR&#95;MESSAGES
from invenio.errorlib import get&#95;msgs&#95;for&#95;code&#95;list, register&#95;errors

def handle&#95;error(error):
    errorlist = get&#95;msgs&#95;for&#95;code&#95;list([error])

    &#35; error is a tuple of error name, arguments => we only need the name
    if CFG&#95;WEBMESSAGE&#95;ERROR&#95;MESSAGES[error[0]]:
        print("Error in webmessage: %s" % errorlist[0][1])
    else:
        for error in errorlist:
            print("Error: %s" % error[1])
        register&#95;errors(errorlist, 'error')
</pre>
<h2>4. <a name="troubleshooting">Troubleshooting</a></h2>

<p>MiscUtil can generate errors. See miscutil&#95;config.py for a complete list.
One can see below some usual errors and their solutions:</p>
<dl>
<dt><b><code>ERR&#95;MISCUTIL&#95;IMPORT&#95;ERROR</code></b></dt>
<dd>The <code>&lt;module-name&gt;&#95;config.py</code> file has not been found. Check it
has the correct name and is deployed.<br />
Check that the error is named following this pattern:
<pre>
    WRN&#95;&lt;MODULE-NAME&gt;&#95;WARNING&#95;NAME or
    ERR&#95;&lt;MODULE-NAME&gt;&#95;WARNING&#95;NAME
</pre>
</dd>

<dt><b><code>ERR&#95;MISCUTIL&#95;NO&#95;DICT</code></b></dt>
<dd>No dictionary could be found in <code>&lt;module-name&gt;&#95;config.py</code>. Check
that your dictionary is correctly named:
<pre>
    CFG&#95;&LT;MODULENAME&GT;&#95;ERROR&#95;MESSAGES
</pre>
You could also have inverted errors and warnings if only one dictionary was provided.<br/>
This can also happen when using direct API if the <code>stream</code> argument is misspelled.
</dd>

<dt><b><code>ERR&#95;MISCUTIL&#95;NO&#95;MESSAGE&#95;IN&#95;DICT</code></b></dt>
<dd>A dictionary was found but not the error in it. You probably misspelled
<code>error&#95;name</code>, or inverted errors and warnings dictionaries.
</dd>

<dt><b><code>ERR&#95;MISCUTIL&#95;UNDEFINED&#95;ERROR</code></b></dt>
<dd>The library couldn't guess the name of module. Check that the error name is beginning
with <code>ERR&#95;MODULE-NAME&#95;</code> or <code>WRN&#95;MODULE-NAME&#95;</code>. This library uses
underscores as separators to guess module name.
</dd>

<dt><b><code>ERR&#95;MISCUTIL&#95;TOO&#95;MANY&#95;ARGUMENT</code></b></dt>
<dd>As the library was rendering the display of error, a surnumerous text substitute was
found (surnumerous are ignored for final rendering, and this error is appened to list of errors):
<pre>
    # Module knights:
    'ERR&#95;KNIGHTS': '&#95;("We are the knights who say %s!")'
    errors = ('ERR&#95;KNIGHTS', 'ni', 'ni')
</pre>
</dd>

<dt><b><code>ERR&#95;MISCUTIL&#95;TOO&#95;FEW&#95;ARGUMENT</code></b></dt>
<dd>Not enough arguments (text substitutes) were given for an error. Missing ones are
replaced by <code>'???'</code>:
<pre>
    # Module knights
    'ERR&#95;KNIGHTS': '&#95;("We are the knights who say %s! We demand a %s")'
    errors = ('ERR&#95;KNIGHTS', 'ni') # so, where is the shrubbery??
</pre>
</dd>

<dt><b><code>ERR&#95;MISCUTIL&#95;BAD&#95;ARGUMENT&#95;TYPE</code></b></dt>
<dd>Your arguments (text substitutes) did not match with the error declaration<br />
e.g. inversion between integer (<code>%i</code>) and string (<code>%s</code>)
</dd>
</dl>
